﻿/*******************************************************************************************************************************************
Modification History:
********************************************************************************************************************************************
Date		Author		    Description
********************************************************************************************************************************************
05/07/2011	Brian Kuhn	    Created ServiceBusBase class
*******************************************************************************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Xml;

namespace Concise
{
    /// <summary>
    /// Provides an abstract base for a node in an event-driven messaging engine that provides a means of sending and receiving messages 
    /// between distributed systems.
    /// </summary>
    public abstract class ServiceBusBase : IServiceBus
    {
        //=======================================================================================================
        //  Constructors
        //=======================================================================================================
        #region ServiceBusBase()
        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceBusBase"/> class.
        /// </summary>
        protected ServiceBusBase()
        {

        }
        #endregion

        #region ServiceBusBase(object target)
        /// <summary>
        /// Initializes a new instance of <see cref="ServiceBusBase"/> class using the specified <paramref name="target"/> 
        /// instance on which subscription callback delegates are invoked.
        /// </summary>
        /// <param name="target">The class instance on which subscription callback delegates are invoked.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="target"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        protected ServiceBusBase(object target) : this()
        {
            this.Target = target;
        }
        #endregion

        //=======================================================================================================
        //  Protected Properties
        //=======================================================================================================
        #region Subscriptions
        /// <summary>
        /// Gets the delegate methods to execute when a message with the registered type is received.
        /// </summary>
        /// <value>
        /// A <see cref="Dictionary{Type, List}"/> collection that contains the <see langword="delegate"/> methods 
        /// to execute when a message with the registered type is received.
        /// </value>
        protected Dictionary<Type, List<Subscription>> Subscriptions
        {
            get
            {
                if (_serviceBusSubscriptions == null)
                {
                    _serviceBusSubscriptions = new Dictionary<Type, List<Subscription>>();
                }
                return _serviceBusSubscriptions;
            }
        }
        private Dictionary<Type, List<Subscription>> _serviceBusSubscriptions;
        #endregion

        #region Target
        /// <summary>
        /// Gets or sets the class instance on which subscription callback delegates are invoked 
        /// when the target of the delegate cannot be inferred.
        /// </summary>
        /// <value>
        /// The class instance on which subscription callback delegates are invoked when the target of the delegate cannot be inferred. 
        /// The default value is a <see langword="null"/> reference (Nothing in Visual Basic).
        /// </value>
        /// <exception cref="ArgumentNullException">The <paramref name="value"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        protected object Target
        {
            get
            {
                return _serviceBusCallbackTarget;
            }

            set
            {
                Guard.AgainstNullReference(value, "value");

                if(!Object.Equals(_serviceBusCallbackTarget, value))
                {
                    _serviceBusCallbackTarget = value;
                }
            }
        }
        private object _serviceBusCallbackTarget;
        #endregion

        //=======================================================================================================
        //  Protected Methods
        //=======================================================================================================
        #region OnMessageReceived(object message, IMessageContext context)
        /// <summary>
        /// Raises the <see cref="MessageReceived"/> event.
        /// </summary>
        /// <param name="message">The message that was received.</param>
        /// <param name="context">The context information for the <paramref name="message"/>.</param>
        protected void OnMessageReceived(object message, IMessageContext context)
        {
            this.OnMessageReceived(new MessageReceivedEventArgs(message, context));
        }
        #endregion

        #region OnMessageReceived(MessageReceivedEventArgs e)
        /// <summary>
        /// Raises the <see cref="MessageReceived"/> event.
        /// </summary>
        /// <param name="e">A <see cref="MessageReceivedEventArgs"/> that contains the event data.</param>
        protected void OnMessageReceived(MessageReceivedEventArgs e)
        {
            var handler = this.MessageReceived;

            if (handler != null)
            {
                handler(this, e);
            }

            NotifySubscribers(this.Subscriptions, e.Message, e.Context);
        }
        #endregion

        #region OnPublish(IEnumerable<object> messages)
        /// <summary>
        /// Publishes the specified <paramref name="messages"/>.
        /// </summary>
        /// <param name="messages">The messages to publish.</param>
        protected virtual void OnPublish(IEnumerable<object> messages)
        {
            // TODO: Magic happens here
        }
        #endregion

        #region OnSend(UniqueId destination, IEnumerable<object> messages)
        /// <summary>
        /// Sends the supplied <paramref name="messages"/> to the specified <paramref name="destination"/>.
        /// </summary>
        /// <param name="destination">The unique identifier of the service bus node to send the <paramref name="messages"/> to.</param>
        /// <param name="messages">The messages to send.</param>
        protected virtual void OnSend(UniqueId destination, IEnumerable<object> messages)
        {
            // TODO: Magic happens here
        }
        #endregion

        #region OnSubscribe<TMessage>(Action<object, IMessageContext> callback, ISpecification<object> criteria, object target)
        /// <summary>
        /// Subscribes to messages of type <typeparamref name="TMessage"/>.
        /// </summary>
        /// <typeparam name="TMessage">The type of message to subscribe to.</typeparam>
        /// <param name="callback">The <see langword="delegate"/> method to execute when a message of type <typeparamref name="TMessage"/> is received. Can be <see langword="null"/>.</param>
        /// <param name="criteria">
        /// A <see cref="ISpecification{TMessage}"/> that defines the set of criteria a message of type <typeparamref name="TMessage"/> 
        /// must meet for the <paramref name="callback"/> to be invoked. Can be <see langword="null"/>.
        /// </param>
        /// <param name="target">The optional class instance on which the subscription <paramref name="callback"/> delegate is invoked.</param>
        protected virtual void OnSubscribe<TMessage>(Action<TMessage, IMessageContext> callback, ISpecification<TMessage> criteria, object target = null)
        {
            var type    = typeof(TMessage);
            target      = this.GetSubscriber(target, callback);
            
            // If callback was provided, validate its target and register it to be notified
            if (callback != null)
            {
                Guard.Against<ArgumentException>(target == null, Properties.Resources.ServiceBusBase_NullCallbackTargetMessage);

                List<Subscription> subscriptions    = null;
                if (!this.Subscriptions.TryGetValue(type, out subscriptions))
                {
                    subscriptions   = new List<Subscription>();
                    this.Subscriptions.Add(type, subscriptions);
                }

                var subscription = new Subscription
                {
                    Callback    = new WeakAction<TMessage, IMessageContext>(target, callback),
                    Criteria    = criteria,
                    MessageType = type
                };

                if (!subscriptions.Contains(subscription))
                {
                    subscriptions.Add(subscription);
                }
            }

            // Remove defunct subscriptions each time a message is received
            RemoveDefunctSubscriptions(this.Subscriptions);

            // TODO: Magic happens here
        }
        #endregion

        #region OnSubscriptionChanged(IEnumerable<Type> added, IEnumerable<Type> removed)
        /// <summary>
        /// Raises the <see cref="SubscriptionChanged"/> event.
        /// </summary>
        /// <param name="added">The messages types that were added. Can be <see langword="null"/>.</param>
        /// <param name="removed">The messages types that were removed. Can be <see langword="null"/>.</param>
        protected void OnSubscriptionChanged(IEnumerable<Type> added, IEnumerable<Type> removed)
        {
            this.OnSubscriptionChanged(new SubscriptionChangedEventArgs(added, removed));
        }
        #endregion

        #region OnSubscriptionChanged(SubscriptionChangedEventArgs e)
        /// <summary>
        /// Raises the <see cref="SubscriptionChanged"/> event.
        /// </summary>
        /// <param name="e">A <see cref="SubscriptionChangedEventArgs"/> that contains the event data.</param>
        protected void OnSubscriptionChanged(SubscriptionChangedEventArgs e)
        {
            var handler = this.SubscriptionChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }
        #endregion

        #region OnUnsubscribe(Type type)
        /// <summary>
        /// Unsubscribes from messages with the specified <paramref name="type"/>.
        /// </summary>
        /// <param name="type">The message type to unsubscribe from. Can be <see langword="null"/>.</param>
        /// <remarks>
        /// If the message <paramref name="type"/> being unsubscribed from is a <see langword="null"/> reference, 
        /// all messages types should be unsubscribed from.
        /// </remarks>
        protected virtual void OnUnsubscribe(Type type)
        {
            // Clear or remove subscriptions based on the message type
            if (type == null)
            {
                this.Subscriptions.Clear();
            }
            else if (this.Subscriptions.ContainsKey(type))
            {
                this.Subscriptions.Remove(type);
            }

            // TODO: Magic happens here
        }
        #endregion

        //=======================================================================================================
        //  Private Methods
        //=======================================================================================================
        #region GetSubscriber<TMessage>(object target, Action<TMessage, IMessageContext> callback)
        /// <summary>
        /// Resolves the appropriate invocation target for a callback delegate method.
        /// </summary>
        /// <typeparam name="TMessage">The type of message being subscribed to.</typeparam>
        /// <param name="target">The explicitly specified invocation target.</param>
        /// <param name="callback">The callback delegate to infer invocation target from.</param>
        /// <returns>
        /// The appropriate invocation target for a callback delegate method, or a <see langword="null"/> 
        /// reference if invocation target cannot be determined.
        /// </returns>
        /// <remarks>
        /// Subscriber preference order is: 
        /// <list type="bullet">
        /// <item><description>The specified <paramref name="target"/>.</description></item>
        /// <item><description>The <see cref="Delegate.Target"/> of the <paramref name="callback"/>.</description></item>
        /// <item><description>The <see cref="Target"/> that was specified when instantiating the service bus.</description></item>
        /// </list>
        /// </remarks>
        private object GetSubscriber<TMessage>(object target, Action<TMessage, IMessageContext> callback)
        {
            Guard.AgainstNullReference(callback, "callback");

            if (target != null)
            {
                return target;
            }
            else if (callback.Target != null)
            {
                return callback.Target;
            }
            else
            {
                return this.Target;
            }
        }
        #endregion

        #region Implements(Type source, Type target)
        /// <summary>
        /// Determines whether the supplied instance type implemented the specified interface type. 
        /// </summary>
        /// <param name="source">The instance type to validate.</param>
        /// <param name="target">The interface type used to search the <paramref name="source"/> for.</param>
        /// <returns>
        /// <see langword="true"/> if the <paramref name="source"/> instance implements the <paramref name="target"/> interface; otherwise, <see langword="false"/>.
        /// </returns>
        private static bool Implements(Type source, Type target)
        {
            if (target == null || source == null)
            {
                return false;
            }

            var interfaces = source.GetInterfaces();
            foreach (var currentInterface in interfaces)
            {
                if (currentInterface == target)
                {
                    return true;
                }
            }

            return false;
        }
        #endregion

        #region NotifySubscribers(Dictionary<Type, List<Subscription>> subscriptions, object message, IMessageContext context)
        /// <summary>
        /// Invokes subscription callbacks that a message has been received.
        /// </summary>
        /// <param name="subscriptions">The collection of subscriptions to invoke callback methods for.</param>
        /// <param name="message">The message that was received.</param>
        /// <param name="context">The context information for the <paramref name="message"/>.</param>
        private static void NotifySubscribers(Dictionary<Type, List<Subscription>> subscriptions, object message, IMessageContext context)
        {
            if (subscriptions == null)
            {
                return;
            }

            RemoveDefunctSubscriptions(subscriptions);

            if(message != null)
            {
                // Build list of subscriptions for the message that was received
                var subscribersToNotify = new List<Subscription>();
                var type                = message.GetType();

                List<Subscription> subscribers = null;
                if (subscriptions.TryGetValue(type, out subscribers))
                {
                    var snapshot    = subscribers.Take(subscribers.Count).ToList();
                    foreach (var subscriber in snapshot)
                    {
                        if (ShouldBeNotified(subscriber, message))
                        {
                            subscribersToNotify.Add(subscriber);
                        }
                    }
                }

                // Invoke callback for selected subscriptions
                foreach (var subscription in subscribersToNotify)
                {
                    var action  = subscription.Callback as IDeferredExecute;

                    if(action != null)
                    {
                        // Invoke deferred execute of callback asynchronously
                        ThreadPool.QueueUserWorkItem(
                            (object state) =>
                            {
                                var parameters  = state as Tuple<IDeferredExecute, object, IMessageContext>;
                                if(parameters != null)
                                {
                                    try
                                    {
                                        parameters.Item1.Execute(parameters.Item2, parameters.Item3);
                                    }
                                    catch (Exception exception)
                                    {
                                        System.Diagnostics.Debug.WriteLine(exception.Message);
                                    }
                                }
                            },
                            new Tuple<IDeferredExecute, object, IMessageContext>(action, message, context)
                        );
                    }
                }
            }
        }
        #endregion

        #region RemoveDefunctSubscriptions(ConcurrentDictionary<Type, IList<TokenizedWeakAction>> subscriptions)
        /// <summary>
        /// Removes defunct message types and subscription registrations from the specified <paramref name="subscriptions"/> collection.
        /// </summary>
        /// <param name="subscriptions">The collection of subscriptions to remove defunct message types and subscription registrations from.</param>
        private static void RemoveDefunctSubscriptions(Dictionary<Type, List<Subscription>> subscriptions)
        {
            if (subscriptions == null)
            {
                return;
            }

            var defunctMessageTypes = new List<Type>();

            foreach (var entry in subscriptions)
            {
                var defunctSubscriptions    = new List<Subscription>();

                foreach (var subscription in entry.Value)
                {
                    if (subscription.Callback == null || !subscription.Callback.IsAlive)
                    {
                        defunctSubscriptions.Add(subscription);
                    }
                }

                foreach (var subscription in defunctSubscriptions)
                {
                    entry.Value.Remove(subscription);
                }

                if (entry.Value.Count == 0)
                {
                    defunctMessageTypes.Add(entry.Key);
                }
            }

            foreach (var messageType in defunctMessageTypes)
            {
                if (subscriptions.ContainsKey(messageType))
                {
                    subscriptions.Remove(messageType);
                }
            }
        }
        #endregion

        #region ShouldBeNotified(Subscription subscription, object message)
        /// <summary>
        /// Returns a value indicating if the callback for the specified <paramref name="subscription"/> should be invoked.
        /// </summary>
        /// <param name="subscription">The subscription to evaluate.</param>
        /// <param name="message">The message that was received.</param>
        /// <returns>
        /// <see langword="true"/> if the callback for the specified <paramref name="subscription"/> should be invoked; otherwise, <see langword="false"/>.
        /// </returns>
        private static bool ShouldBeNotified(Subscription subscription, object message)
        {
            var messageType = message != null ? message.GetType() : null;

            return subscription != null
                && subscription.Callback != null
                && subscription.Callback.IsAlive
                && subscription.Callback.Target != null

                &&
                (
                    Type.Equals(subscription.MessageType, messageType)
                    || Implements(subscription.MessageType, messageType)
                )

                &&
                (
                    (subscription.Criteria == null) || (subscription.Criteria != null && subscription.Criteria.IsSatisfiedBy(message))
                );
        }
        #endregion

        //=======================================================================================================
        //  IDisposable Implementation
        //=======================================================================================================
        #region Dispose()
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

        #region Dispose(bool disposing)
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <param name="disposing">
        /// A value indicating if managed resources should be disposed of.
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
            lock (_serviceBusDisposalLock)
            {
                if (!_serviceBusIsDisposed)
                {
                    _serviceBusIsDisposed = true;

                    this.OnDispose(disposing);
                }
            }
        }
        private readonly object _serviceBusDisposalLock = new Object();
        private bool _serviceBusIsDisposed;
        #endregion

        #region OnDispose(bool disposing)
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <param name="disposing">
        /// A value indicating if managed resources should be disposed of.
        /// </param>
        protected virtual void OnDispose(bool disposing)
        {
            if (disposing)
            {
            }
        }
        #endregion

        //=======================================================================================================
        //  IServiceBus Implementation
        //=======================================================================================================
        #region MessageReceived
        /// <summary>
        /// Occurs when a message is received.
        /// </summary>
        /// <remarks>
        /// This event is raised each time a message is sent from the message bus to this node.
        /// </remarks>
        public event EventHandler<MessageReceivedEventArgs> MessageReceived;
        #endregion

        #region SubscriptionChanged
        /// <summary>
        /// Occurs when a message is received.
        /// </summary>
        /// <remarks>
        /// This event is raised each time there is a change in the subscriptions maintained by the message bus for this node.
        /// </remarks>
        public event EventHandler<SubscriptionChangedEventArgs> SubscriptionChanged;
        #endregion

        #region Endpoint
        /// <summary>
        /// The endpoint address of the content-based publish/subscribe message bus.
        /// </summary>
        /// <value>
        /// A <see cref="Uri"/> that represents the unique network address of the content-based publish/subscribe message bus. 
        /// The default value is a <see langword="null"/> reference (Nothing in Visual Basic).
        /// </value>
        /// <exception cref="ArgumentNullException">The <paramref name="value"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        public Uri Endpoint
        {
            get
            {
                return _serviceBusEndpoint;
            }

            protected set
            {
                Guard.AgainstNullReference(value, "value");

                if(!Uri.Equals(_serviceBusEndpoint, value))
                {
                    _serviceBusEndpoint = value;
                }
            }
        }
        private Uri _serviceBusEndpoint;
        #endregion

        #region SubscriberId
        /// <summary>
        /// Gets the unique identifier of this service bus node.
        /// </summary>
        /// <value>
        /// A <see cref="UniqueId"/> that identifies this service bus node. 
        /// The default value is a new <see cref="UniqueId"/> constructed with a <see cref="Guid.NewGuid()">new globally unique identifier</see>.
        /// </value>
        /// <exception cref="ArgumentNullException">The <paramref name="value"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        public UniqueId SubscriberId
        {
            get
            {
                return _serviceBusSubscriberId;
            }

            protected set
            {
                Guard.AgainstNullReference(value, "value");

                if (!UniqueId.Equals(_serviceBusSubscriberId, value))
                {
                    _serviceBusSubscriberId = value;
                }
            }
        }
        private UniqueId _serviceBusSubscriberId = new UniqueId(Guid.NewGuid());
        #endregion

        #region Publish(params object[] messages)
        /// <summary>
        /// Publishes the specified <paramref name="messages"/>.
        /// </summary>
        /// <param name="messages">An <see cref="Array"/> of type <see cref="Object"/> that contains the  messages to publish.</param>
        /// <remarks>
        /// Calling <see cref="Publish"/> will transmit the specified <paramref name="messages"/> to the message bus, 
        /// which will match the messages against a set of subscribers based on message type and then forward each 
        /// message to the appropriate service bus nodes.
        /// </remarks>
        /// <exception cref="ArgumentNullException">The <paramref name="messages"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        public void Publish(params object[] messages)
        {
            Guard.AgainstNullReference(messages, "messages");

            this.OnPublish(messages);
        }
        #endregion

        #region Send(UniqueId destination, params object[] messages)
        /// <summary>
        /// Sends the supplied <paramref name="messages"/> to the specified <paramref name="destination"/>.
        /// </summary>
        /// <param name="destination">The unique identifier of the service bus node to send the <paramref name="messages"/> to.</param>
        /// <param name="messages">An <see cref="Array"/> of type <see cref="Object"/> that contains the messages to send.</param>
        /// <remarks>
        /// Calling <see cref="Send"/> will transmit the supplied <paramref name="messages"/> to the message bus, which will 
        /// forward the messages to the service bus node that matches the specified <paramref name="destination"/> identifier.
        /// </remarks>
        /// <exception cref="ArgumentNullException">The <paramref name="destination"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        /// <exception cref="ArgumentNullException">The <paramref name="messages"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        public void Send(UniqueId destination, params object[] messages)
        {
            Guard.AgainstNullReference(destination, "destination");
            Guard.AgainstNullReference(messages, "messages");

            this.OnSend(destination, messages);
        }
        #endregion

        #region Subscribe<TMessage>()
        /// <summary>
        /// Subscribes to messages of type <typeparamref name="TMessage"/>.
        /// </summary>
        /// <typeparam name="TMessage">The type of message to subscribe to.</typeparam>
        /// <returns>
        /// The <see cref="IServiceBus"/> object that this method was called on.
        /// </returns>
        public IServiceBus Subscribe<TMessage>()
        {
            this.OnSubscribe<TMessage>(callback: null, criteria: null, target: null);

            return this;
        }
        #endregion

        #region Subscribe<TMessage>(Action<TMessage, IMessageContext> callback)
        /// <summary>
        /// Subscribes to messages of type <typeparamref name="TMessage"/>.
        /// </summary>
        /// <typeparam name="TMessage">The type of message to subscribe to.</typeparam>
        /// <param name="callback">The <see langword="delegate"/> method to execute when a message of type <typeparamref name="TMessage"/> is received.</param>
        /// <returns>
        /// The <see cref="IServiceBus"/> object that this method was called on.
        /// </returns>
        /// <exception cref="ArgumentNullException">The <paramref name="callback"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        /// <exception cref="ArgumentException">The <see cref="Delegate.Target"/> of the <paramref name="callback"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        public IServiceBus Subscribe<TMessage>(Action<TMessage, IMessageContext> callback)
        {
            Guard.AgainstNullReference(callback, "callback");

            this.OnSubscribe<TMessage>(callback: callback, criteria: null, target: null);

            return this;
        }
        #endregion

        #region Subscribe<TMessage>(Action<TMessage, IMessageContext> callback, ISpecification<TMessage> criteria)
        /// <summary>
        /// Subscribes to messages of type <typeparamref name="TMessage"/>.
        /// </summary>
        /// <typeparam name="TMessage">The type of message to subscribe to.</typeparam>
        /// <param name="callback">The <see langword="delegate"/> method to execute when a message of type <typeparamref name="TMessage"/> is received.</param>
        /// <param name="criteria">
        /// A <see cref="ISpecification{TMessage}"/> that defines the set of criteria a message of type <typeparamref name="TMessage"/> 
        /// must meet for the <paramref name="callback"/> to be invoked.
        /// </param>
        /// <returns>
        /// The <see cref="IServiceBus"/> object that this method was called on.
        /// </returns>
        /// <exception cref="ArgumentNullException">The <paramref name="callback"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        /// <exception cref="ArgumentNullException">The <paramref name="criteria"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        /// <exception cref="ArgumentException">The <see cref="Delegate.Target"/> of the <paramref name="callback"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        public IServiceBus Subscribe<TMessage>(Action<TMessage, IMessageContext> callback, ISpecification<TMessage> criteria)
        {
            Guard.AgainstNullReference(callback, "callback");
            Guard.AgainstNullReference(criteria, "criteria");

            this.OnSubscribe<TMessage>(callback: callback, criteria: criteria, target: null);

            return this;
        }
        #endregion

        #region Unsubscribe()
        /// <summary>
        /// Unubscribes from all message types.
        /// </summary>
        /// <returns>
        /// The <see cref="IServiceBus"/> object that this method was called on.
        /// </returns>
        public IServiceBus Unsubscribe()
        {
            this.OnUnsubscribe(null);

            return this;
        }
        #endregion

        #region Unsubscribe<TMessage>()
        /// <summary>
        /// Unsubscribes from messages of type <typeparamref name="TMessage"/>.
        /// </summary>
        /// <typeparam name="TMessage">The type of message to unsubscribe from.</typeparam>
        /// <returns>
        /// The <see cref="IServiceBus"/> object that this method was called on.
        /// </returns>
        public IServiceBus Unsubscribe<TMessage>()
        {
            this.OnUnsubscribe(typeof(TMessage));

            return this;
        }
        #endregion
    }
}
